package cn.stimd.spring.context.event;

import org.springframework.core.ResolvableType;
import org.springframework.util.ObjectUtils;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by Stimd
 * 项目地址：https://gitee.com/stimd/spring-wheel
 * 公众号：Java编程探微
 */
public class SimpleApplicationEventMulticaster implements ApplicationEventMulticaster {
    //所有的监听器集合
    private final Set<ApplicationListener<?>> allListeners = new LinkedHashSet<>();
    //按事件类型分类的监听器列表缓存
    private final Map<ListenerCacheKey, Set<ApplicationListener<?>>> retrieverCache = new ConcurrentHashMap<>(64);
    private final Object retrievalMutex = new Object();


    @Override
    public void multicastEvent(final ApplicationEvent event) {
        ResolvableType type = ResolvableType.forInstance(event);
        for (final ApplicationListener listener : getApplicationListeners(event, type)) {
            listener.onApplicationEvent(event);
        }
    }


    protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
        Object source = event.getSource();
        Class<?> sourceType = (source != null ? source.getClass() : null);
        ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

        //尝试从缓存中获取指定事件的监听器列表
        Set<ApplicationListener<?>> listeners = this.retrieverCache.get(cacheKey);
        if (listeners != null) {
            return listeners;
        }

        synchronized (this.retrievalMutex) {
            listeners = retrieveApplicationListeners(eventType, sourceType);
            this.retrieverCache.put(cacheKey, listeners);
            return listeners;
        }
    }


    private Set<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, Class<?> sourceType) {
        Set<ApplicationListener<?>> listeners = new LinkedHashSet<>();
        for (ApplicationListener<?> listener : allListeners) {
            if(supportsEvent(listener, eventType, sourceType)){
                listeners.add(listener);
            }
        }
        return listeners;
    }


    protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, Class<?> sourceType) {
        //1) ApplicationListenerMethodAdapter 是 GenericApplicationListener 接口的直接子类，表示监听方法
        //2) 否则 listener 是一个监听器类，需要使用 GenericApplicationListenerAdapter 来包装
        GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
                (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
        return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
    }


    @Override
    public void addApplicationListener(ApplicationListener<?> listener) {
        synchronized (this.retrievalMutex) {
            this.allListeners.add(listener);
            this.retrieverCache.clear();
        }
    }


    private static final class ListenerCacheKey implements Comparable<ListenerCacheKey> {
        private final ResolvableType eventType;
        private final Class<?> sourceType;

        public ListenerCacheKey(ResolvableType eventType, Class<?> sourceType) {
            this.eventType = eventType;
            this.sourceType = sourceType;
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }
            ListenerCacheKey otherKey = (ListenerCacheKey) other;
            return (ObjectUtils.nullSafeEquals(this.eventType, otherKey.eventType) &&
                    ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType));
        }

        @Override
        public int hashCode() {
            return (ObjectUtils.nullSafeHashCode(this.eventType) * 29 + ObjectUtils.nullSafeHashCode(this.sourceType));
        }

        @Override
        public String toString() {
            return "ListenerCacheKey [eventType = " + this.eventType + ", sourceType = " + this.sourceType.getName() + "]";
        }

        @Override
        public int compareTo(ListenerCacheKey other) {
            int result = 0;
            if (this.eventType != null) {
                result = this.eventType.toString().compareTo(other.eventType.toString());
            }
            if (result == 0 && this.sourceType != null) {
                result = this.sourceType.getName().compareTo(other.sourceType.getName());
            }
            return result;
        }
    }
}
